﻿Imports Windows.UI.Xaml.Media.Animation
Imports Windows.UI.Xaml.Shapes

Public Class Sprite
    Implements IDisposable

#Region "DECLARATIONS"

    Private _spriteShape As Rectangle                   'rettangolo che rappresenta fisicamente l'oggetto nel canvas
    Private SpriteBrush As ImageBrush                   'pennello per disegnare l'immagine della spritesheet
    Private OffsetTransform As New TranslateTransform   'offset per spostarsi all'interno della spritesheet
    Private RotationTransform As New RotateTransform    'rotazione

    Private _currentSpritesheetColumn As Integer        'indice della colonna corrente nella spritesheet
    Private _currentSpritesheetRow As Integer           'indice della riga corrente nella spritesheet

    Private _key As String                              'chiave che identifica univocamente l'oggetto
    Private _spriteType As String                       'stringa che definisce il "tipo" dell'oggetto (personaggio, nemico ecc..)
    Private _spritesheet As SpriteSheetStructure

    Private _position As Point                          'posizione corrente dell'oggetto nel canvas
    Private _parent As ScrollableBackground = Nothing   'riferimento all'oggetto ScrollableBackground che contiene il personaggio
    Private _collisionEnabled As Boolean                'indica se per questo oggetto vanno controllate le collisioni
    Private _pixelArray() As Byte                       'array di byte contenente il buffer di pixel dell'immagine corrente

    Public RandomGenerator As New Random                'generatore di numeri che può sempre servire

    Private _properties As Dictionary(Of String, Double)    'dizionario con le properietà dell'oggetto

    Private _animations As Dictionary(Of String, SpriteAnimation) = Nothing 'animazioni applicabili in base alla spritesheet
    Private _currentAnimationKey As String               'la chiave dell'animazione corrente
    Private AnimationStoryboard As Storyboard           'timer per l'animazione della spritesheet

    Private _soundEffects As Dictionary(Of String, MediaElement)

#End Region

#Region "PROPERTIES"

    ''' <summary>
    ''' Restituisce l'oggetto Rectangle da utilizzare nel canvas di gioco per visualizzare la sprite
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property SpriteShape As Rectangle
        Get
            Return Me._spriteShape
        End Get
    End Property

    ''' <summary>
    ''' Restituisce l'indice di colonna della posizione corrente all'interno della spritesheet
    ''' </summary>
    Public ReadOnly Property CurrentSpritesheetColumn As Integer
        Get
            Return Me._currentSpritesheetColumn
        End Get
    End Property

    ''' <summary>
    ''' Restituisce l'indice di riga della posizione corrente all'interno della spritesheet
    ''' </summary>
    Public ReadOnly Property CurrentSpritesheetRow As Integer
        Get
            Return Me._currentSpritesheetRow
        End Get
    End Property

    ''' <summary>
    ''' Restituisce o imposta la chiave dell'oggetto
    ''' </summary>
    Public Property Key As String
        Get
            Return _key
        End Get
        Set(value As String)
            Me._key = value
        End Set
    End Property

    Public Property SpriteType As String
        Get
            Return Me._spriteType
        End Get
        Set(value As String)
            Me._spriteType = value
        End Set
    End Property

    ''' <summary>
    ''' Restituisce o imposta il riferimento all'oggetto ScrollableBackground che contiene l'oggetto
    ''' </summary>
    Public Shadows Property Parent As ScrollableBackground
        Get
            Return Me._parent
        End Get
        Set(value As ScrollableBackground)
            Me._parent = value
        End Set
    End Property

    ''' <summary>
    ''' Restituisce o imposta la posizione dell'oggetto nel canvas dello ScrollableBackground
    ''' </summary>
    Public Property Position As Point
        Get
            Return Me._position
        End Get
        Set(value As Point)
            Me._position = value
            'se è già in un contenitore imposto la posizione direttamente
            If Me.Parent IsNot Nothing Then
                Me.SpriteShape.SetValue(Canvas.LeftProperty, value.X)
                Me.SpriteShape.SetValue(Canvas.TopProperty, value.Y)
            End If
        End Set
    End Property

    ''' <summary>
    ''' Restituisce o imposta il valore che indica se l'oggetto è soggetto al controllo delle collisioni
    ''' </summary>
    Public Property isCollisionEnabled As Boolean
        Get
            Return Me._collisionEnabled
        End Get
        Set(value As Boolean)
            Me._collisionEnabled = value
        End Set
    End Property

    Public ReadOnly Property Size As Size
        Get
            Return New Size(Me.SpriteShape.Width, Me.SpriteShape.Height)
        End Get
    End Property

    Public ReadOnly Property PixelArray As Byte()
        Get
            Return Me._pixelArray
        End Get
    End Property

    Public ReadOnly Property Animations As Dictionary(Of String, SpriteAnimation)
        Get
            Return Me._animations
        End Get
    End Property

    Public ReadOnly Property CurrentAnimationKey As String
        Get
            Return Me._currentAnimationKey
        End Get
    End Property

    ''' <summary>
    ''' Restituisce o imposta un dizionario di proprietà personalizzate
    ''' </summary>
    Public Property Properties As Dictionary(Of String, Double)
        Get
            Return Me._properties
        End Get
        Set(value As Dictionary(Of String, Double))
            Me._properties = value
        End Set
    End Property

    ''' <summary>
    ''' Restituisce o imposta un dizionario di MediaElement utilizzati per gli effetti sonori dell'oggetto Sprite
    ''' </summary>
    Public Property SoundEffects As Dictionary(Of String, MediaElement)
        Get
            Return Me._soundEffects
        End Get
        Set(value As Dictionary(Of String, MediaElement))
            Me._soundEffects = value
        End Set
    End Property

#End Region

#Region "CONSTRUCTORS"

    Public Sub New(width As Integer, height As Integer, initialPosition As Point)


        Me.SpriteBrush = New ImageBrush
        Me.SpriteBrush.Stretch = Stretch.None
        Me.SpriteBrush.AlignmentX = AlignmentX.Left
        Me.SpriteBrush.AlignmentY = AlignmentY.Top

        _spriteShape = New Rectangle
        _spriteShape.Height = height
        _spriteShape.Width = width
        _spriteShape.Fill = Me.SpriteBrush
        _spriteShape.RenderTransform = RotationTransform
        _spriteShape.RenderTransformOrigin = New Point(0.5, 0.5)

        Me.Position = initialPosition

        Me._currentAnimationKey = ""

        'inizializzo già il timer per le animazioni
        Me.AnimationStoryboard = New Storyboard
        'AddHandler Me.AnimationStoryboard.Completed, AddressOf AnimationLoop

    End Sub

#End Region

#Region "Public Methods"

    ''' <summary>
    ''' Inizializza la struttura SpriteSheet dell'oggetto con l'immagine impostata
    ''' </summary>
    ''' <param name="uriSource">Uri dell'immagine da aggiungere</param>
    ''' <param name="columnCount">Numero di colonne della Spritesheet</param>
    ''' <param name="rowCount">Numero di righe della Spritesheet</param>
    Public Async Function SetSpriteSheet(ByVal uriSource As Uri, columnCount As Integer, rowCount As Integer) As Task(Of Boolean)

        Dim source As WriteableBitmap = Await BitmapFactory.[New](1, 1).FromContent(uriSource)

        _spritesheet.SheetSource = source
        _spritesheet.ColumnCount = columnCount
        _spritesheet.RowCount = rowCount

        Me.SpriteBrush.ImageSource = Me._spritesheet.SheetSource
        'Me._currentSpritesheetColumn = 0
        'Me._currentSpritesheetRow = 0

        _pixelArray = _spritesheet.SheetSource.ToByteArray

        Return True

    End Function
    ''' <summary>
    ''' Inizializza la struttura SpriteSheet dell'oggetto con l'immagine impostata
    ''' </summary>
    ''' <param name="source">Immagine da aggiungere</param>
    ''' <param name="columnCount">Numero di colonne della Spritesheet</param>
    ''' <param name="rowCount">Numero di righe della Spritesheet</param>
    Public Sub SetSpriteSheet(ByVal source As WriteableBitmap, columnCount As Integer, rowCount As Integer)

        _spritesheet.SheetSource = source
        _spritesheet.ColumnCount = columnCount
        _spritesheet.RowCount = rowCount

        Me.SpriteBrush.ImageSource = Me._spritesheet.SheetSource
        'Me._currentSpritesheetColumn = 0
        'Me._currentSpritesheetRow = 0

        _pixelArray = _spritesheet.SheetSource.ToByteArray

    End Sub

    ''' <summary>
    ''' Imposta il frame visualizzato in base alla posizione nella spritesheet
    ''' </summary>
    ''' <param name="columnIndex">Numero della colonna (coordinata X)</param>
    ''' <param name="rowIndex">Numero della riga (coordinata Y)</param>
    Public Sub SetCurrentFrame(columnIndex As Integer, rowIndex As Integer)

        Me._currentSpritesheetColumn = columnIndex
        Me._currentSpritesheetRow = rowIndex

        Me.OffsetTransform.X = -_spriteShape.Width * Me._currentSpritesheetColumn
        OffsetTransform.Y = -_spriteShape.Height * Me._currentSpritesheetRow
        Me.SpriteBrush.Transform = OffsetTransform

    End Sub

    ''' <summary>
    ''' imposta il frame corrente su quello successivo
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub SetNextFrame(direction As SpriteAnimation.enAnimationDirection)

        Select Case direction
            Case SpriteAnimation.enAnimationDirection.FORWARD

                'devo avanzare di una colonna
                If Me._currentSpritesheetColumn < Me._spritesheet.ColumnCount - 1 Then
                    'non è l'ultima colonna, quindi avanzo di 1
                    Me._currentSpritesheetColumn += 1
                Else
                    'se sono già sull'ultima colonna devo cambiare riga
                    'a meno che non sia l'ultima riga, in tal caso sono alla fine della spritesheet
                    If Me._currentSpritesheetRow < Me._spritesheet.RowCount - 1 Then
                        'non è l'ultima riga, quindi riparto dalla prima colonna della riga successiva
                        Me._currentSpritesheetColumn = 0
                        Me._currentSpritesheetRow += 1
                    End If
                End If
            Case SpriteAnimation.enAnimationDirection.BACKWARDS
                'devo tornare indietro di una colonna 
                If Me._currentSpritesheetColumn > 0 Then
                    'non è la prima colonna, quindi indietro di di 1
                    Me._currentSpritesheetColumn -= 1
                Else
                    'se sono già sulla prima colonna devo cambiare riga
                    'a meno che non sia la prima riga, in tal caso sono all'inizio della spritesheet
                    If Me._currentSpritesheetRow > 0 Then
                        'non è la prima riga, quindi riparto dall'ultima colonna della riga precedente
                        Me._currentSpritesheetColumn = Me._spritesheet.ColumnCount - 1
                        Me._currentSpritesheetRow -= 1
                    End If
                End If
        End Select


        SetCurrentFrame(_currentSpritesheetColumn, _currentSpritesheetRow)

    End Sub

    ''' <summary>
    ''' Aggiunge un'animazione alla lista delle animazioni
    ''' </summary>
    ''' <param name="key">Stringa identificativa dell'animazione</param>
    ''' <param name="startColumnIndex">Indice di colonna del primo frame dell'animazione all'interno della spritesheet</param>
    ''' <param name="startRowIndex">Indice di riga del primo frame dell'animazione all'interno della spritesheet</param>
    ''' <param name="endColumnIndex">Indice di colonna dell'ultimo frame dell'animazione all'interno della spritesheet</param>
    ''' <param name="endRowIndex">Indice di riga dell'ultimo frame dell'animazione all'interno della spritesheet</param>
    ''' <param name="loopBehavior">Imposta cosa fare alla fine dell'animazione</param>
    ''' <param name="direction">Imposta la direzione dell'animazione</param>
    Public Sub AddAnimation(key As String, startColumnIndex As Integer, startRowIndex As Integer, endColumnIndex As Integer, endRowIndex As Integer, loopBehavior As SpriteAnimation.enLoopBehaviorType, direction As SpriteAnimation.enAnimationDirection)

        If Me.Animations Is Nothing Then Me._animations = New Dictionary(Of String, SpriteAnimation)

        Dim anim As New SpriteAnimation
        anim.StartColumnIndex = startColumnIndex
        anim.StartRowIndex = startRowIndex
        anim.EndColumnIndex = endColumnIndex
        anim.EndRowIndex = endRowIndex
        anim.LoopBehavior = loopBehavior
        anim.AnimationDirection = direction

        Me.Animations.Add(key, anim)

    End Sub

    ''' <summary>
    ''' Avvia l'animazione relativa alla chiave indicata
    ''' </summary>
    ''' <param name="key">Stringa identificativa dell'animazione</param>
    ''' <param name="frameRate">Velocità espressa in frame per secondo</param>
    Public Sub StartAnimation(Optional key As String = "", Optional frameRate As Integer = 0)

        Me.StopAnimation()

        'imposto l'animazione corrente
        If key <> "" Then
            Me._currentAnimationKey = key
        End If

        'imposto la velocità del loop
        If frameRate > 0 Then
            AnimationStoryboard.Duration = TimeSpan.FromSeconds(1 / frameRate)
        End If

        'imposto il primo frame
        Me.SetCurrentFrame(Me.Animations(_currentAnimationKey).StartColumnIndex, Me.Animations(_currentAnimationKey).StartRowIndex)

        'avvio l'animazione
        AddHandler Me.AnimationStoryboard.Completed, AddressOf AnimationLoop
        Me.AnimationStoryboard.Begin()

    End Sub

    Public Sub StopAnimation()

        Me.AnimationStoryboard.Stop()
        RemoveHandler Me.AnimationStoryboard.Completed, AddressOf AnimationLoop

    End Sub

    ''' <summary>
    ''' Sposta l'oggetto Sprite alla posizione indicata
    ''' </summary>
    ''' <param name="destination">Punto in cui spostare l'oggetto</param>
    Public Sub Move(destination As Point)

        Me.Position = destination

    End Sub

    ''' <summary>
    ''' Ruota l'oggetto sprite dei gradi indicati
    ''' </summary>
    ''' <param name="degrees">Valore dell'inclinazione in gradi</param>
    ''' <remarks></remarks>
    Public Sub Rotate(degrees As Double)

        Me.RotationTransform.Angle = degrees

    End Sub

    ''' <summary>
    ''' Aggiunge un MediaElement al dizionario dei suoni dell'oggetto Sprite
    ''' </summary>
    ''' <param name="key">Stringa identificativa del suono</param>
    ''' <param name="mediaUri">Uri del file multimediale da aggiungere</param>
    Public Sub AddSound(key As String, mediaUri As Uri, volume As Decimal)

        If Me._soundEffects Is Nothing Then
            Me._soundEffects = New Dictionary(Of String, MediaElement)
        End If

        Dim media As New MediaElement
        media.AutoPlay = False
        media.AudioCategory = AudioCategory.GameEffects
        media.Source = mediaUri
        media.Volume = volume

        Me._soundEffects.Add(key, media)

        'se l'oggetto è già inserito nel canvas, aggiungo il mediaelement anche lì
        If Me.Parent IsNot Nothing Then
            Me.Parent.BackgroundCanvas.Children.Add(media)
        End If

    End Sub

    ''' <summary>
    ''' Riproduce il file multimediale associato alla chiave
    ''' </summary>
    ''' <param name="key">Stringa che rappresenta la chiave del MediaElement da riprodurre</param>
    Public Sub PlaySound(key As String)

        If Me._soundEffects(key).CurrentState = MediaElementState.Playing Then Me._soundEffects(key).Stop()
        Me._soundEffects(key).Play()

    End Sub

    ''' <summary>
    ''' Funzione che ritorna True se il pixel alla posizione di pointToCheck è trasparente
    ''' </summary>
    ''' <param name="pointToCheck">Punto da verificare sull'immagine</param>
    Public Function IsPixelTransparent(pointToCheck As Point) As Boolean

        If pointToCheck.X < 0 OrElse pointToCheck.Y < 0 OrElse pointToCheck.X >= _spritesheet.SheetSource.PixelWidth Then
            Return True
        End If

        Try

            'se c'è un'animazione in corso devo verificare spostandomi al frame corrente
            Dim offset As Integer = 0
            If Me._spritesheet.ColumnCount > 1 OrElse Me._spritesheet.RowCount > 1 Then
                offset = (Me.CurrentSpritesheetRow * Me._spritesheet.ColumnCount * (Me.Size.Width * 4) * Me.Size.Height) 'qui sono alla fine della riga nella spritesheet precedente a quella che mi interessa
            End If

            Dim B As Integer = Me.PixelArray(offset + (Math.Truncate(pointToCheck.Y) * (Me._spritesheet.ColumnCount * Me.Size.Width * 4) + ((Me.CurrentSpritesheetColumn * Me.Size.Width * 4) + (Math.Truncate(pointToCheck.X) * 4))))
            Dim G As Integer = Me.PixelArray(offset + ((Math.Truncate(pointToCheck.Y) * (Me._spritesheet.ColumnCount * Me.Size.Width * 4) + ((Me.CurrentSpritesheetColumn * Me.Size.Width * 4) + (Math.Truncate(pointToCheck.X) * 4))) + 1))
            Dim R As Integer = Me.PixelArray(offset + ((Math.Truncate(pointToCheck.Y) * (Me._spritesheet.ColumnCount * Me.Size.Width * 4) + ((Me.CurrentSpritesheetColumn * Me.Size.Width * 4) + (Math.Truncate(pointToCheck.X) * 4))) + 2))
            Dim A As Integer = Me.PixelArray(offset + ((Math.Truncate(pointToCheck.Y) * (Me._spritesheet.ColumnCount * Me.Size.Width * 4) + ((Me.CurrentSpritesheetColumn * Me.Size.Width * 4) + (Math.Truncate(pointToCheck.X) * 4))) + 3))

            If A = 0 Then 'AndAlso R = 0 AndAlso G = 0 AndAlso B = 0 Then
                Return True
            Else
                Return False
            End If

        Catch ex As Exception
            Return True
        End Try


    End Function

    ''' <summary>
    ''' Restituisce una nuova istanza clonando l'istanza corrente
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function Clone() As Sprite

        Dim newSprite As New Sprite(Me.Size.Width, Me.Size.Height, Me.Position)

        newSprite.SetSpriteSheet(Me._spritesheet.SheetSource, Me._spritesheet.ColumnCount, Me._spritesheet.RowCount)

        'aggiungo eventuali animazioni
        If Me.Animations IsNot Nothing AndAlso Me.Animations.Count > 0 Then
            For i As Integer = 0 To Me.Animations.Count - 1
                newSprite.AddAnimation(Me.Animations.ElementAt(i).Key, _
                                       Me.Animations.ElementAt(i).Value.StartColumnIndex, Me.Animations.ElementAt(i).Value.StartRowIndex, _
                                       Me.Animations.ElementAt(i).Value.EndColumnIndex, Me.Animations.ElementAt(i).Value.EndRowIndex, _
                                       Me.Animations.ElementAt(i).Value.LoopBehavior, _
                                       Me.Animations.ElementAt(i).Value.AnimationDirection)
            Next
        End If

        'imposto il frame corrente spritesheet
        newSprite.SetCurrentFrame(Me.CurrentSpritesheetColumn, Me.CurrentSpritesheetRow)

        'imposto le collisioni
        newSprite.isCollisionEnabled = Me.isCollisionEnabled

        'la chiave la lascio vuota
        newSprite.Key = ""

        newSprite.Parent = Me.Parent

        'imposto le proprietà
        newSprite.Properties = New Dictionary(Of String, Double)
        If Me.Properties.Count > 0 Then
            For i As Integer = 0 To Me.Properties.Count - 1
                newSprite.Properties.Add(Me.Properties.ElementAt(i).Key, Me.Properties.ElementAt(i).Value)
            Next i
        End If

        'imposto il tipo
        newSprite.SpriteType = Me.SpriteType

        Return newSprite

    End Function

#End Region

#Region "Private Methods"

    Private Sub AnimationLoop(sender As Object, e As Object)

        'se non sono ancora alla fine dell'animazione passo al frame successivo
        If Me.CurrentSpritesheetColumn <> Me.Animations(_currentAnimationKey).EndColumnIndex OrElse Me.CurrentSpritesheetRow <> Me.Animations(_currentAnimationKey).EndRowIndex Then
            Me.SetNextFrame(Me.Animations(_currentAnimationKey).AnimationDirection)
            Me.AnimationStoryboard.Begin()
        Else
            'se sono sul'ultimo devo ricominciare solo se è attivo un loop
            Select Case Me.Animations.Item(_currentAnimationKey).LoopBehavior
                Case SpriteAnimation.enLoopBehaviorType.RESTART
                    'se l'animazione è a ripetizione, ricomincio da capo
                    'quindi reimposto il primo frame
                    Me.SetCurrentFrame(Me.Animations(_currentAnimationKey).StartColumnIndex, Me.Animations(_currentAnimationKey).StartRowIndex)
                    'riavvio l'animazione
                    Me.AnimationStoryboard.Begin()
                Case SpriteAnimation.enLoopBehaviorType.REVERSE
                    'se l'animazione è del tipo avanti e indietro, inverto la direzione
                    Me.Animations(_currentAnimationKey).InvertDirection()
                    'aggiorno subito il frame per non perderne uno
                    Me.SetNextFrame(Me.Animations(_currentAnimationKey).AnimationDirection)
                    'riavvio l'animazione
                    Me.AnimationStoryboard.Begin()
            End Select
        End If

    End Sub

#End Region

#Region "IDisposable Support"
    Private disposedValue As Boolean ' Per rilevare chiamate ridondanti

    ' IDisposable
    Protected Overridable Sub Dispose(disposing As Boolean)
        If Not Me.disposedValue Then
            If disposing Then
                RemoveHandler AnimationStoryboard.Completed, AddressOf AnimationLoop

                _spriteShape = Nothing
                SpriteBrush = Nothing
                OffsetTransform = Nothing
                RotationTransform = Nothing
                _parent = Nothing
                RandomGenerator = Nothing
                If _properties IsNot Nothing Then _properties.Clear()
                _properties = Nothing
                If _animations IsNot Nothing Then _animations.Clear()
                _animations = Nothing
                AnimationStoryboard = Nothing
                If _soundEffects IsNot Nothing Then _soundEffects.Clear()
                _soundEffects = Nothing
            End If

            ' TODO: liberare risorse non gestite (oggetti non gestiti) ed eseguire l'override del seguente Finalize().
            ' TODO: impostare campi di grandi dimensioni su null.
        End If
        Me.disposedValue = True
    End Sub

    ' TODO: eseguire l'override di Finalize() solo se Dispose(ByVal disposing As Boolean) dispone del codice per liberare risorse non gestite.
    'Protected Overrides Sub Finalize()
    '    ' Non modificare questo codice. Inserire il codice di pulizia in Dispose(ByVal disposing As Boolean).
    '    Dispose(False)
    '    MyBase.Finalize()
    'End Sub

    ' Questo codice è aggiunto da Visual Basic per implementare in modo corretto il modello Disposable.
    Public Sub Dispose() Implements IDisposable.Dispose
        ' Non modificare questo codice. Inserire il codice di pulizia in Dispose(disposing As Boolean).
        Dispose(True)
        GC.SuppressFinalize(Me)
    End Sub
#End Region

End Class

Structure SpriteSheetStructure

    Dim ColumnCount As Integer
    Dim RowCount As Integer
    Dim SheetSource As WriteableBitmap

End Structure

Public Class SpriteAnimation

    Protected Friend StartColumnIndex As Integer
    Protected Friend StartRowIndex As Integer

    Protected Friend EndColumnIndex As Integer
    Protected Friend EndRowIndex As Integer

    Protected Friend LoopBehavior As enLoopBehaviorType
    Enum enLoopBehaviorType
        ONE_SHOT = 0
        RESTART = 1
        REVERSE = 2
    End Enum

    Protected Friend AnimationDirection As enAnimationDirection
    Enum enAnimationDirection
        FORWARD = 0
        BACKWARDS = 1
    End Enum

    Public Sub InvertDirection()

        Dim oldStartRow As Integer = Me.StartRowIndex
        Dim oldStartColumn As Integer = Me.StartColumnIndex

        Me.StartColumnIndex = Me.EndColumnIndex
        Me.StartRowIndex = Me.EndRowIndex

        Me.EndColumnIndex = oldStartColumn
        Me.EndRowIndex = oldStartRow

        If Me.AnimationDirection = enAnimationDirection.BACKWARDS Then
            Me.AnimationDirection = enAnimationDirection.FORWARD
        Else
            Me.AnimationDirection = enAnimationDirection.BACKWARDS
        End If
    End Sub

End Class